﻿#include "http-client.h"

#define MAX_T 1
#define CLIENTCERT "cert/client/client.crt"
#define CLIENTKEY "cert/client/client.key"
#define CAFILE "cert/ca/ca.crt"

static pthread_mutex_t *lock_cs;
static long             lock_count[CRYPTO_num_locks()];

// 加载请求内容
std::string load_request_content() {
    std::ifstream     f("conf/request.txt");
    std::stringstream ss;

    std::string line;
    while (std::getline(f, line)) {
        ss << line << std::endl;
    }
    return ss.str();
}

// 线程ID回调函数
pthread_t pthreads_thread_id(void) {
    pthread_t ret;
    ret = pthread_self();
    return (ret);
}

// 锁回调函数
void pthreads_locking_callback(int mode, int type, char *file,
                               int line) {
    if (mode & CRYPTO_LOCK) {
        pthread_mutex_lock(&(lock_cs[type]));
        lock_count[type]++;
    } else {
        pthread_mutex_unlock(&(lock_cs[type]));
    }
}

// 验证回调函数
int verify_callback(int ok, X509_STORE_CTX *ctx) {
    printf("verify_callback\n");
    return ok;
}

// 使用带密码的私钥文件
int HttpClient::SSL_CTX_use_PrivateKey_file_pass(SSL_CTX *ctx, char *filename, char *pass) {
    EVP_PKEY *pkey = NULL;
    BIO      *key  = NULL;
    key            = BIO_new(BIO_s_file());
    BIO_read_filename(key, filename);
    pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, pass);
    if (pkey == NULL) {
        printf("PEM_read_bio_PrivateKey err");
        return -1;
    }
    if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) {
        printf("SSL_CTX_use_PrivateKey err\n");
        return -1;
    }
    BIO_free(key);
    return 1;
}

// 请求线程主函数
void *client_thread_main(void *arg) {
    int                err, buflen, read;
    int                sd;
    SSL_CTX           *ctx = (SSL_CTX *)arg;
    struct sockaddr_in dest_addr;
    SOCKET             sock;
    PHOSTENT           phe;
    WSADATA            wsaData;
    SSL               *ssl;
    X509              *server_cert;
    char              *str;
    char              *buf;
    SSL_METHOD        *meth;
    FILE              *fp;
    fd_set             fd;
    struct timeval     tv;
    int                retries = 1;

    // 超时5s
    tv.tv_sec  = 5;
    tv.tv_usec = 0;

    const char *host   = "www.qq.com";
    const char *port   = "443";
    std::string ip_str = my_util_nets::host2ip(host, port);

again:
    // 重试超过3次就退出
    if (retries++ > 3) {
        printf("retries > 3, exit\n");
        HttpClient::free(*ssl, sock);
        return ((void *)-1);
    }

    sock                      = socket(AF_INET, SOCK_STREAM, 0);
    dest_addr.sin_family      = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr(ip_str.c_str());
    dest_addr.sin_port        = htons(443);

    err = connect(sock, (PSOCKADDR)&dest_addr, sizeof(dest_addr));
    if (err < 0) {
        perror("connect err");
        Sleep(1);
        goto again;
    }

    ssl = SSL_new(ctx);
    if (ssl == NULL) {
        perror("ss new err");
        closesocket(sock);
        return ((void *)-1);
    }

    // 将套接字绑定到SSL会话
    SSL_set_fd(ssl, sock);
    // 设置SNI扩展
    SSL_set_tlsext_host_name(ssl, host);

    err = SSL_connect(ssl);
    if (err < 0) {
        perror("SSL_connect err");
        HttpClient::free(*ssl, sock);
        return ((void *)-1);
    }
    printf("SSL connection using %s\n", SSL_get_cipher(ssl));

    /*server_cert = SSL_get_peer_certificate(ssl);
    printf("Server certificate:\n");
    str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0);
    printf("\t subject: %s\n", str);
    OPENSSL_free(str);
    str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0);
    printf("\t issuer: %s\n", str);
    OPENSSL_free(str);
    X509_free(server_cert);*/

    // 请求体
    // err = SSL_get_shutdown(ssl);

    // 发包
    std::string strWrite =
        "GET / HTTP/1.1\r\n"
        "Host: www.qq.com\r\n"
        "Connection: keep-alive\r\n"
        "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n"
        "Accept-Encoding: gzip, deflate, br, zstd\r\n"
        "Accept-Language: zh-CN,zh;q=0.9\r\n"
        "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36\r\n\r\n";
    std::string request_content      = load_request_content();
    const char *request_content_char = strWrite.c_str();
    err                              = SSL_write(ssl, request_content_char, strlen(request_content_char));
    // err = SSL_write(ssl, strWrite.c_str(), strlen(strWrite.c_str()));
    // err = send(sock, request_content_char, strlen(request_content_char), 0);

    if (err < 0) {
        perror("SSL_write err");
        HttpClient::free(*ssl, sock);
        return ((void *)-1);
    }

    // set to non-blocking
    unsigned long iMode = 1;
    if (ioctlsocket(sock, FIONBIO, (unsigned long *)&iMode) < 0) {
        HttpClient::free(*ssl, sock);
        return ((void *)-1);
    }

    // 接收响应
    while (true) {
        // 接收服务器的数据
        FD_ZERO(&fd);
        FD_SET(sock, &fd);
        err = select(sock + 1, &fd, NULL, NULL, &tv);
        // err = SSL_get_shutdown(ssl);
        if (err > 0) {
            if (FD_ISSET(sock, &fd)) {
                std::stringstream ss;
                buf = (char *)malloc(4096);
                while ((err = SSL_read(ssl, buf, sizeof(buf) - 1)) > 0) {
                    ss << std::string(buf, 0, err);
                }
                ss << '\0';

                /*char *un_comp_chr = nullptr;
                my_util_nets::un_compress(ss.str().c_str(), ss.str().length(), un_comp_chr, 100);*/

                printf("\n\nResponse:\n%s\n", ss.str().c_str());
                break;
            }
        } else if (err == 0) {
            // 超时处理
            printf("Select timeout\n");
            break;
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
                continue;

            // 错误处理
            perror("Select error");
            break;
        }
    }

    HttpClient::free(*ssl, sock);
    return ((void *)0);
}

int HttpClient::test() {
    int                err, buflen, read;
    int                sd;
    struct sockaddr_in dest_sin;
    SOCKET             sock;
    PHOSTENT           phe;
    WORD               wVersionRequested;
    WSADATA            wsaData;
    SSL_CTX           *ctx;
    SSL               *ssl;
    X509              *server_cert;
    char              *str;
    char               buf[1024];
    const SSL_METHOD  *method;
    int                i;
    pthread_t          pid[MAX_T];

    SSL_library_init();
    SSL_load_error_strings();
    SSLeay_add_ssl_algorithms();
    method = SSLv23_client_method();
    ctx    = SSL_CTX_new(method);

    if (ctx == NULL) {
        printf("ssl ctx new eer\n");
        return -1;
    }

    // 设置协议版本 - 支持 TLS 1.2 及以上
    // SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);

    // 启用服务器名称指示 (SNI)
    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); //  按位与运算

    /*if (SSL_CTX_use_certificate_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(3);
    }

    if (SSL_CTX_use_PrivateKey_file(ctx, (char *)CLIENTKEY, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(4);
    }*/

    // 设置验证模式 - 验证服务器证书
    // SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    // 加载信任的CA证书 - 使用系统默认CA证书
    /*if (!SSL_CTX_set_default_verify_paths(ctx)) {
        fprintf(stderr, "Failed to set default CA verify paths\n");
        ERR_print_errors_fp(stderr);
    }*/

    OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
    OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));

    for (i = 0; i < CRYPTO_num_locks(); i++) {
        lock_count[i] = 0;
        pthread_mutex_init(&(lock_cs[i]), NULL);
    }

    CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
    CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);

    for (i = 0; i < MAX_T; i++) {
        err = pthread_create(&(pid[i]), NULL, client_thread_main, (void *)ctx);
        if (err != 0) {
            printf("pthread_create err\n");
            continue;
        }
    }
    for (i = 0; i < MAX_T; i++) {
        pthread_join(pid[i], NULL);
    }
    SSL_CTX_free(ctx);
    printf("test ok\n");
    return 0;
}

int HttpClient::free(SSL &ssl, SOCKET s) {
    SSL_shutdown(&ssl); /* send SSL/TLS close_notify */
    SSL_free(&ssl);
    closesocket(s);
    return 0;
}